<?php

/**
 * http协程网络请求，内部调试类，请直接用 HttpRequest.php
 */

namespace Lib\Base;

class HttpCoRequest
{

    private $m_handle;
    private $m_host;
    private $m_port;
    private $m_path;
    private $m_query;
    private $m_path_query;
    private $options;

    public const HTTP_SUCCESS = 0;
    public const HTTP_URL_INVALID = -201;
    public const HTTP_CONNECT_ERROR = -202;
    public const HTTP_TIMEOUT = -203;
    public const HTTP_FAIL = -204;

    /**
     * http请求构造函数
     *
     * HttpCoRequest constructor.
     * @param array $options
     */
    public function __construct($options = ['decode' => true])
    {
        $this->m_handle = null;
        $this->options = $options;
    }

    /**
     * http请求析构函数
     */
    public function __destruct()
    {
        if ($this->m_handle != null) {
            $this->m_handle->close();
            $this->m_handle = null;
            unset($this->m_handle);
        }
    }

    /**
     * 设置配置
     *
     * @param $header
     */
    private function setOptions($header)
    {
        if (!isset($header['Host'])) {
            $header['Host'] = $this->m_host;
        }
        if (!isset($header['Accept'])) {
            $header['Accept'] = 'text/html,application/xhtml+xml,application/xml';
        }
        $this->m_handle->setHeaders($header);

        if (isset($this->options['cookie']) && $this->options['cookie']) {
            $this->m_handle->setCookies(Request::cookieParam());
        }
    }

    /**
     * @function connect($ip, $port = 80, $ssl = false)
     * @brief http连接
     * @param $ip string 连接的host地址
     * @param $port string 连接的port地址
     * @param $ssl bool 是否走https连接
     * @return 0 成功 -202 连接失败
     */
    private function connect($host, $port = 80, $ssl = false)
    {
        if ($this->m_handle == null) {

            if ($port == 443) {
                $ssl = true;
            }

            $this->m_handle = new \Swoole\Coroutine\Http\Client($host, $port, $ssl);

            if (!is_object($this->m_handle)) {
                return self::HTTP_CONNECT_ERROR;
            }
        }

        return self::HTTP_SUCCESS;
    }

    /**
     * 发起get请求
     *
     * @param string $url 请求url，如果有get参数，直接拼接在url后面
     * @param array $header http请求头部信息
     * @return array ['ret'=>'成功：0，失败：错误码  -201 url格式非法 -202 连接失败 -203 get超时 -204 get失败 ', 'msg'=>'具体的错误信息', data=>'请求数据。如果返回结果是json，并且设置的decode=true,返回数据，否则返回结果集字符串']
     */

    public function get($url, $header = array())
    {
        recordLog("http url = " . $url . ', header = '. var_export($header, true), __FILE__, __LINE__, LP_INFO);

        //开始时间
        $startTime = microtime(true);

        //判断url格式是否符合标准
        if (!filter_var($url, FILTER_VALIDATE_URL)) {
            return ['ret' => self::HTTP_URL_INVALID, 'msg' => 'url格式错误', 'data' => []];
        }

        //解析url
        $this->parseUrl($url);

        //连接
        $ret = $this->connect($this->m_host, $this->m_port);
        if ($ret != self::HTTP_SUCCESS) {
            return ['ret' => $ret, 'msg' => '连接失败', 'data' => []];
        }

        //设置头
        $this->setOptions($header);

        //设置超时时间
        $timeout = $this->options['timeout'] ?? 3;
        $this->m_handle->set(['timeout' => $timeout]);

        //请求
        $ret = $this->m_handle->get($this->m_path_query);

        //结束时间
        $endTime = microtime(true);

        //耗时
        $costTime = round((($endTime - $startTime) * 1000), 3);
        recordLog("http get ret = " . $ret . ", errCode = " . $this->m_handle->errCode . ", costTime = " . $costTime , __FILE__, __LINE__, LP_INFO);

        if (!$ret) {
            if ($this->m_handle->errCode == 110) {
                return ['ret' => self::HTTP_TIMEOUT, 'msg' => '连接超时', 'data' => []];
            }
            return ['ret' => self::HTTP_FAIL, 'msg' => '请求失败', 'data' => []];
        }

        //状态码不等于200
        if ($this->m_handle->statusCode != 200) {
            return ['ret' => self::HTTP_FAIL, 'msg' => "请求失败: " . $this->m_handle->statusCode, 'data' => []];
        }

        return $this->parseResult($this->m_handle->body);
    }

    /**
     * 发起post请求
     *
     * @param string $url 请求url，如果有get参数，直接拼接在url后面
     * @param string|array 请求数据。这个参数可以是 urlencoded 后的字符串，类似'para1=val1&para2=val2&...'，也可以使用一个以字段名为键值，字段数据为值的数组。
     * @param array $header http请求头部信息
     * @return array ['ret'=>'成功：0，失败：错误码 -201 url格式非法 -204 post失败 -203 post超时', 'msg'=>'具体的错误信息', data=>'请求数据。如果返回结果是json，并且设置的decode=true,返回数据，否则返回结果集字符串']
     */
    public function post($url, $param, $header = array())
    {
        recordLog("http post: url = " . $url . ', header = '. var_export($header, true) . ', $postData = '. var_export($param, true), __FILE__, __LINE__, LP_DEBUG);

        //开始时间
        $startTime = microtime(true);

        if (!filter_var($url, FILTER_VALIDATE_URL)) {
            return ['ret' => self::HTTP_URL_INVALID, 'msg' => 'url格式错误', 'data' => []];
        }

        //解析url
        $this->parseUrl($url);

        //连接
        $ret = $this->connect($this->m_host, $this->m_port);

        if ($ret != self::HTTP_SUCCESS) {
            return ['ret' => $ret, 'msg' => '连接失败', 'data' => []];
        }


        //设置头
        $this->setOptions($header);

        //设置超时时间
        $timeout = $this->options['timeout'] ?? 3;
        $this->m_handle->set(['timeout' => $timeout]);

        $ret = $this->m_handle->post($this->m_path_query, $param);

        //结束时间
        $endTime = microtime(true);

        //耗时
        $costTime = round((($endTime - $startTime) * 1000), 3);
        recordLog("http post ret = " . $ret . ", errCode = " . $this->m_handle->errCode . ", costTime = " . $costTime, __FILE__, __LINE__, LP_INFO);

        if (!$ret) {
            if ($this->m_handle->errCode == 110) {
                return ['ret' => self::HTTP_TIMEOUT, 'msg' => '连接超时', 'data' => []];
            }
            return ['ret' => self::HTTP_FAIL, 'msg' => '请求失败', 'data' => []];
        }

        //状态码不等于200
        if ($this->m_handle->statusCode != 200) {
            return ['ret' => self::HTTP_FAIL, 'msg' => "请求失败: " . $this->m_handle->statusCode, 'data' => []];
        }

        return $this->parseResult($this->m_handle->body);
    }

    /**
     * 解析url
     *
     * @param $url
     */
    private function parseUrl($url)
    {
        $arr = parse_url($url);
        $this->m_host = $arr['host'];
        if (isset($arr['scheme']) && $arr['scheme'] == 'https') {
            $this->m_port = isset($arr['port']) ? $arr['port'] : 443;
        } else {
            $this->m_port = isset($arr['port']) ? $arr['port'] : 80;
        }
        $this->m_path = isset($arr['path']) ? $arr['path'] : '/';
        if (array_key_exists("query", $arr)) {
            $this->m_query = $arr['query'];
            $this->m_path_query = $this->m_path . '?' . $this->m_query;
        } else {
            $this->m_query = "";
            $this->m_path_query = $this->m_path;
        }
    }

    /**
     * 解析结果
     *
     * @param $result
     * @return array
     */
    private function parseResult($result)
    {
        //日志记录
        recordLog("http return = " . $result , __FILE__, __LINE__, LP_INFO);

        //返回值进行编码转换
        if (isset($this->options['convert']) && count($this->options['convert']) == 2) {
            $result = Common::charsetConvert($result, $this->options['convert'][0], $this->options['convert'][1]);
        }

        if (isset($this->options['decode']) && $this->options['decode']) {
            $data = json_decode($result, true);
            if (is_null($data)) {
                return ['ret' => json_last_error(), 'msg' => json_last_error_msg(), 'data' => []];
            } else {
                return ['ret' => 0, 'msg' => '', 'data' => $data];
            }
        } else {
            return ['ret' => 0, 'msg' => '', 'data' => $result];
        }
    }
}

